<template>
    <div class="console-page forbid-select">
        <el-drawer v-model="showDbMenu" direction="ltr" size="400px" :show-close="false" :with-header="false"
                   class="forbid-select connection-dialog" @open="useModalSize()" @close="useModalSize(false)"
                   append-to-body>
            <div class="background">
                <el-collapse accordion class="dbinfo" :model-value="openCollaspeItem">
                    <el-collapse-item :name="item" v-for="(item, index) in Object.keys(dbInfo)" :key="index"
                                      :disabled="!dbInfo[item].length">
                        <template #title>
                            {{ item }}
                        </template>
                        <ul>
                            <li v-for="(c_item, c_index) in dbInfo[item]" class="el-dropdown-menu" :key="c_index"
                                :class="`el-dropdown-menu__item cursor-pointer ${connectionId === c_item.sequence ? 'selected' : ''}`"
                                @click="setConnectionId(c_item)">
                                <el-icon v-show="connectionId === c_item.sequence">
                                    <checkIcon/>
                                </el-icon>
                                <span>{{ c_item.name }}</span>
                            </li>
                        </ul>
                    </el-collapse-item>
                </el-collapse>
            </div>
        </el-drawer>
        <el-row class="full-percent">
            <el-col class="result-col" :span="24">
                <div class="background">
                    <el-row :style="`height: calc(30% + ${dragDelta}px);`" ref="editorContainerRef">
                        <el-watermark :font="font" style="width: 100%;height: 100%;" :content="connectName">
                            <aceEditor v-model="sql.current" :options="{ lang: 'sql' }" ref="aceRef"
                                       v-permission="$permission.Write"/>
                        </el-watermark>
                    </el-row>
                    <el-row class="drag cursor-pointer" @mousedown="startDrag"/>
                    <el-row :style="`height: calc(70% - 5px - ${dragDelta}px);position: relative;`"
                            ref="runInfoContainerRef">
                        <el-icon class="cursor-pointer connection" @click="showDbMenu = true"
                                 v-permission="$permission.Execute">
                            <component :is="getIcon('link')"/>
                        </el-icon>
                        <i :class="`cursor-pointer iconfont icon-playfill play ${connectionId ? '' : 'query-disabled is-disabled'}`"
                           @click="firstQuery" v-permission="$permission.Execute" id="querySql"/>
                        <el-tabs type="border-card" class="full-percent sql-result" v-model="activeTab">
                            <el-tab-pane label="信息" name="info">
                                <div style="padding: 5px;height: calc(100% - 10px);">
                                    <el-scrollbar class="full-percent">
                                        <div v-if="sql.prev">
                                            <highlight :code="sql.prev" class="line sql-content"/>
                                            <div class="line" :style="`color:${runState.status ? 'green' : 'red'}`">
                                                >>{{ runState.message }}
                                            </div>
                                            <div class="line">
                                                <span>执行时间：{{
                                                        sql.runTime.format('YYYY-MM-DD HH:mm:ss.SSS')
                                                    }}</span>
                                                <span v-if="!runState.status">，耗时:{{ sql.cost }}ms</span>
                                            </div>
                                        </div>
                                        <div v-else class="line">
                                            当前未执行SQL
                                        </div>
                                    </el-scrollbar>
                                </div>
                            </el-tab-pane>
                            <el-tab-pane name="history" label="历史记录">
                                <template #label>
                                    <div @dblclick.prevent.stop="getHistory" style="cursor: pointer;">历史记录</div>
                                </template>
                                <div style="height: 100%;width: 100%;" v-permission="$permission.Execute">
                                    <el-scrollbar class="full-percent">
                                        <div v-for="(item, index) in sqlRecord" class="no-wrap history-item"
                                             :key="index" @dblclick="copy(item.sql)">
                                            <div class="history">
                                                <i :class="`cursor-pointer  iconfont icon-playfill play ${connectionId ? '' : 'query-disabled is-disabled'} history-play`"
                                                   @click="playHistory(item.sql)"/>
                                                <div style="display: flex;overflow: hidden;width: 98%;">
                                                    <el-icon v-if="item.ddl"
                                                             style="padding-right: 10px;color: rgb(219 39 39 / 71%);"
                                                             size="20">
                                                        <warning/>
                                                    </el-icon>
                                                    <div class="sql-show-no-wrap">{{ item.sql }}</div>
                                                </div>
                                                <div class="run-info">
                                                    <div>第一次执行时间:{{ item.crtTime }}</div>
                                                    <div>最新执行时间:{{ item.updateTime }}</div>
                                                    <div>执行次数:{{ item.execNum }}</div>
                                                </div>
                                            </div>
                                        </div>
                                    </el-scrollbar>
                                </div>
                            </el-tab-pane>
                            <el-tab-pane label="结果" name="result" :disabled="!data.length && !dataColumn.length"
                                         @click="click">
                                <div style="height: 100%;width: 100%;">
                                    <el-table :data="data" border class="e-page request-table" highlight-current-row
                                              @cell-click="cellClick" header-cell-class-name="el-table-header-cell"
                                              @row-click="rowClick" cell-class-name="el-table-cell"
                                              @cell-dblclick="cellDblClick" ref="resultRef" empty-text=" ">
                                        <el-table-column type="index" width="40" fixed class-name="index" v-if="total">
                                            <template #default="scoped">
                                                <div class="flex-display-center"
                                                     :style="`color:${scoped.$index === currentRowIndex ? 'red' : ''}`">
                                                    <span>{{ scoped.$index + 1 }}</span>
                                                    <el-icon v-show="scoped.$index === currentRowIndex">
                                                        <component :is="getIcon('DArrowRight')"/>
                                                    </el-icon>
                                                </div>
                                            </template>
                                        </el-table-column>
                                        <el-table-column :prop="column" :label="column"
                                                         :width="`${dataColumnWidth[column]}px`"
                                                         v-for="(column, index) in dataColumn" :key="index">
                                            <template #header="scope">
                                                <span v-html="getHeader(scope)"></span>
                                            </template>
                                            <template #default="scoped">
                                                <span>{{ scoped.row[column] }}</span>
                                            </template>
                                        </el-table-column>
                                    </el-table>
                                    <div style="height: 40px;padding: 5px 0;">
                                        <el-pagination background @size-change="SizeChange"
                                                       @current-change="CurrentChange" :current-page="currentPage"
                                                       :page-sizes="pageSizes" :page-size="pageSize"
                                                       layout="total, sizes, prev, pager, next, jumper"
                                                       prev-text="<上一页"
                                                       next-text="下一页>" :total="total">
                                        </el-pagination>
                                    </div>
                                </div>
                            </el-tab-pane>
                        </el-tabs>
                    </el-row>
                </div>
            </el-col>
        </el-row>
    </div>
</template>
<script setup>
import {isArray} from "@vue/shared"
import {getStorage} from "@/utils/storage"
import {getElementSize, getIcon} from "@/utils/tools"
import {
    useCopyText,
    useElMessage,
    useElLoading,
    useElMessageBox,
    useTabPaneAnimation,
    useModalSize,
    useRequest,
    useEventListener
} from "@usehook"
import {config} from "@/utils/setting"
import storeInstance from "@/pinia"

let font = $ref({fontSize: config.value.fontSize, fontWeight: "", fontFamily: config.value.fontFamily})
const checkIcon = getIcon("check")
const warning = getIcon("WarningFilled")
let data = $ref([])
let sqlRecord = $ref([])

const dataColumn = $computed(() => {
    return Object.keys(columnDataMap).map(e => e.toLocaleLowerCase())
})

let draggable = $ref(false)
let move = $ref({start: 0, offset: 0, cacheDelta: 0})
let dragDelta = $ref(0)
let showDbMenu = $ref(false)
const aceRef = $ref(null)
let currentRowIndex = $ref(-1)

let activeTab = $ref("info")

const checkRunSqlPermission = () => {
    return !!document.querySelector("#querySql")
}

const rowClick = (row) => {
    currentRowIndex = data.indexOf(row)
}
useTabPaneAnimation(() => activeTab, (pane) => {
    storeInstance.tagMenuStore.setTabPane(pane)
})
const dataColumnWidth = $computed(() => {
    const width = {}
    for (let item of data) {
        for (let key of Object.keys(item)) {
            const length = width[key]
            let newLength = Math.max((item[key] || '').toString().length, key.length)
            if (!length || newLength > length) {
                width[key] = newLength
            }
        }
    }
    for (let column of dataColumn) {
        width[column] = width[column] > column.length ? width[column] : column.length
    }
    for (let column of dataColumn) {
        let newLength = width[column] * (width[column] <= 10 ? 12 : width[column] <= 20 ? 10 : 8)
        width[column] = newLength > 400 ? 400 : newLength < 150 ? 150 : newLength * 1.1
    }
    return width
})

const openCollaspeItem = $computed(() => {
    const open = []
    for (let key in dbInfo) {
        if (dbInfo[key].length > 0) {
            open.push(key)
        }
    }
    return open
})

const storage = getStorage("sqlEditor")
let sql = $ref({current: "", prev: "", runTime: new Date(), cost: 0})
let connectionId = $ref("")
let connectName = $ref("")
let runState = $ref({status: true, message: ""})
let currentPage = $ref(1)
let pageSizes = [50, 100, 150, 200]
let total = $ref(0)
let pageSize = $ref(pageSizes[0])
let currentHistory = $ref("")
let currentSql = $ref("")
let abortRunSql = $ref(null)
let columnDataMap = $ref({})

const setConnectionId = (c_item) => {
    connectionId = c_item.sequence
    connectName = c_item.name
    sessionStorage.setItem("sqlExecutorConnectionId", connectionId)
}

const SizeChange = (val) => {
    pageSize = val
    currentPage = 1
    play()
}

const CurrentChange = (val) => {
    currentPage = val
    play()
}

const firstQuery = () => {
    pageSize = pageSizes[0]
    currentPage = 1
    currentSql = ""
    play()
}

const cellDblClick = (row, column, cell, _event) => {
    for (let ele of document.body.querySelectorAll(".cell-select")) {
        ele.classList.remove("cell-select")
    }

    if (row[column.label]) {
        cell.classList.add("cell-select")
        useCopyText(row[column.label])
    }
}

const cellClick = (_row, _column, cell, _event) => {
    if (!cell.classList.contains("cell-select")) {
        for (let ele of document.body.querySelectorAll(".cell-select")) {
            ele.classList.remove("cell-select")
        }
    }
}
let resultRef = $ref(null)
const click = (e) => {
    const clickOnTable = e.srcElement.firstElementChild && e.srcElement.firstElementChild.classList.contains("el-table__body")
    const clickOnPagination = e.srcElement.classList.contains("el-pagination")
    if (clickOnTable || clickOnPagination) {
        resultRef.setCurrentRow()
        currentRowIndex = -1
    }
    for (let ele of document.body.querySelectorAll(".cell-select")) {
        ele.classList.remove("cell-select")
    }
}

const copy = (history) => {
    useCopyText(history + ";")
}

const getHistory = () => {
    if (!checkRunSqlPermission()) return
    useRequest({url: "/data/sqlRecord"}).then(respData => {
        sqlRecord = respData.data
    })
}

const playHistory = (history) => {
    currentHistory = history
    firstQuery()
}

const {loading, isloading, close} = useElLoading({text: "数据正在查询", target: ".sql-result", manual: true, delay: 10})

const abortRunRequest = () => {
    if (abortRunSql) {
        abortRunSql()
        abortRunSql = null
    }
}

watch(isloading, (newVal) => {
    if (newVal) {
        nextTick(() => {
            document.querySelector(".loader").addEventListener("dblclick", abortRunRequest)
        })
    }
})

const getHeader = (scope) => {
    const label = `<div>${scope.column.label}</div>`
    const columnType = `<div class='column'>${columnDataMap[scope.column.label]}</div>`
    return `${columnDataMap[scope.column.label] ? label + columnType : label}`
}

const play = async () => {
    if (!connectionId || isloading.value || !checkRunSqlPermission()) {
        currentHistory = ""
        return
    }

    let play = true

    const curSql = (currentSql || currentHistory || aceRef.getSelection() || sql.current).trim()
    if (!curSql) return

    const operate = curSql.split(" ")[0]

    if (['delete', 'drop', 'truncate', 'update', 'alter', 'create', 'insert'].includes(operate.toLowerCase())) {
        await useElMessageBox.error("操作提示", "SQL中包含" + operate + "操作，是否继续?", {
            showCancelButton: true,
            falseInCatch: true
        }).catch(() => {
            play = false
        }).finally(() => {
            currentHistory = ""
        })
    }

    if (!play) return

    runState = {status: true, message: "正在查询"}
    sql.runTime = new Date()
    const start = sql.runTime.getTime()
    sql.prev = curSql

    const runSql = sql.prev.replaceAll(";", "")

    if (sql.prev.length - runSql.length > 1) {
        useElMessage.error("一次只能执行一个SQL")
        runState = {status: false, message: "执行失败, 一次只能执行一个SQL"}
        data = []
        columnDataMap = {}
        activeTab = "info"
        return
    }
    loading()
    const {abort, then} = useRequest({
        url: "/data/sql",
        connectionId,
        sql: runSql,
        method: "post",
        page: currentPage,
        size: pageSize
    })
    abortRunSql = abort
    then((resData) => {
        if (resData && resData.status) {
            total = resData.totalRecord
            data = resData.data.sqlResult || []
            columnDataMap = {}
            if (resData.data.column && resData.data.column.length) {
                for (let col of resData.data.column) {
                    columnDataMap[col.column] = col.displayType
                }
            } else if (isArray(resData.data.sqlResult)) {
                for (let col of Object.keys(resData.data.sqlResult[0])) {
                    columnDataMap[col] = ''
                }
            }
            if (isArray(data)) {
                activeTab = data.length && total ? "result" : "info"
                runState = {status: true, message: "执行成功,结果集:" + total + "条数据"}
            } else {
                activeTab = "info"
                runState = {status: true, message: "执行成功" + (!isNaN(data) ? ",影响行数: " + data : "")}
                data = []
                columnDataMap = {}
            }
        } else {
            activeTab = "info"
            data = []
            columnDataMap = {}
            runState = {status: resData.status, message: resData.message ? resData.message : "执行失败"}
        }
    }).catch((error) => {
        activeTab = "info"
        data = []
        columnDataMap = {}
        runState = {status: false, message: error.cancel ? "查询请求已取消" : "执行失败"}
    }).finally(() => {
        sql.cost = new Date().getTime() - start
        currentHistory = ""
        currentRowIndex = -1
        currentSql = sql.prev
        abortRunSql = null
        close()
    })
}

let dbInfo = $ref({})

onMounted(() => {
    sql.current = storage
    if (!checkRunSqlPermission()) return
    useRequest({url: "/data/connection/getConnection"}).then((resData) => {
        dbInfo = resData.data.db
        const fromSessionDbInfo=Object.values(dbInfo).flatMap(e=>e).findLast(e=>e.sequence===sessionStorage.getItem("sqlExecutorConnectionId"))
        if(fromSessionDbInfo){
            connectionId=fromSessionDbInfo.sequence
            connectName = fromSessionDbInfo.name
        }
    })
    getHistory()
})

const keyMap = (e) => {
    const {key} = e
    if (key === 'F8' && !useModalSize.get()) {
        firstQuery()
    }
}

const startDrag = (e) => {
    move.offset = dragDelta
    draggable = true
    move.start = e.clientY
    move.cacheDelta = 0
    // 禁止用户选择网页中文字
    document.onselectstart = () => false
}

const editorContainerRef = $ref(null)
const runInfoContainerRef = $ref(null)

const onDrag = (e) => {
    if (draggable) {
        let delta = e.clientY - move.start
        if (getElementSize(editorContainerRef.$el, "height") < 100 && delta < move.cacheDelta) return
        if (getElementSize(runInfoContainerRef.$el, "height") < 100 && delta > move.cacheDelta) return
        dragDelta = delta + move.offset
        move.cacheDelta = delta
    }
}

const endDrag = () => {
    draggable = false
    // 允许用户选择网页中文字
    document.onselectstart = null
}

useEventListener("keydown", keyMap)
useEventListener("mousemove", onDrag)
useEventListener("mouseup", endDrag)
</script>

<style lang="scss" scoped>
@import "@usehook/cursor.scss";

:deep(.background) {
    height: 100%;
    border-radius: var(--border-radius);
}

:deep(.sql-result .el-tabs__item) {
    height: 35px;
    font-size: 16px;
    line-height: 20px;
    font-weight: var(--font-weight);
    z-index: -1;
    min-width: 120px;
    border-right-color: var(--el-border-color) !important;
}

:deep(.sql-result.el-tabs--border-card>.el-tabs__content) {
    height: calc(100% - 35px);
    font-size: 16px;
}

:deep(.sql-result .el-table) {
    font-size: 16px !important;
}

:deep(.sql-result .el-table__row) {
    height: 30px !important;
}

:deep(.sql-result .el-table__header) {
    height: 40px !important;
}

:deep(.sql-result .el-table .cell) {
    line-height: 16px !important;
}

:deep(.sql-result .el-tab-pane) {
    height: 100%;
}

:deep(.sql-result .request-table) {
    height: calc(100% - 50px);
}

:deep(.result-col) {
    height: 100%;
}

:deep(.play) {
    position: absolute;
    top: 5px;
    right: 10px;
    z-index: 1;
    font-size: 25px;
    cursor: pointer;
    color: rgb(77 183 154 / 89%)
}

:deep(.connection) {
    position: absolute;
    top: 5px;
    right: 50px;
    z-index: 1;
    font-size: 25px;
    cursor: pointer;
    color: #409eff
}

:deep(.line) {
    padding: 15px;
}

:deep(.dbinfo) {
    border: none;
}

:deep(.dbinfo .el-collapse-item__header) {
    height: calc(var(--font-size) * 3);
    font-weight: var(--font-weight);
    padding-left: 10px;
    font-size: var(--font-size);
    line-height: calc(var(--font-size) * 3);
}

:deep(.dbinfo .el-collapse-item:not(.is-disabled) .el-collapse-item__header) {
    background: rgb(0 0 0 / 3%) !important;
}

:deep(.dbinfo .el-collapse-item__content) {
    padding: 5px;
    font-size: var(--font-size);
    line-height: calc(var(--font-size) * 2);

    ul {
        margin: 0;

        .el-dropdown-menu {
            height: calc(var(--font-size) * 2);
        }
    }
}

:deep(.dbinfo .el-collapse-item__wrap) {
    background: transparent;
}

:deep(.dbinfo .el-collapse-item.is-disabled .el-collapse-item__header) {
    @include cursor-default-important
}

:deep(.dbinfo .el-dropdown-menu) {
    padding: 0;
    background-color: transparent !important;
    backdrop-filter: none !important;
}

:deep(.dbinfo .el-dropdown-menu:hover) {
    background: rgba(135, 135, 135, .2);
}

:deep(.query-disabled) {
    color: rgba(135, 135, 135, .5);
    cursor: default;
}

:deep(.dbinfo .selected) {
    background: #dfdfdf;
}

:deep(.drag) {
    height: 5px;
    cursor: n-resize;
    background: rgb(0 0 0 / 5%);
}

:deep(.el-table--border .el-table__inner-wrapper:after) {
    height: 0 !important;
    display: none;
}

:deep(.history) {
    padding: 10px;
    background: rgb(191 191 191 / 60%);
    border-radius: var(--border-radius);
    position: relative;
}

:deep(.history-item) {
    padding: 5px 5px 0 5px;
}

:deep(.run-info) {
    display: flex;
    padding-top: 15px;
    justify-content: space-between;
    width: 95%;
}

:deep(.history-play) {
    position: absolute;
    right: 5px;
    top: calc(50% - 13px);
}

:deep(.sql-show-no-wrap) {
    /* 让所有子元素平均分配容器宽度 */
    flex: 1;
    /* 确保文本不会换行 */
    white-space: nowrap;
    /* 超出部分显示为省略号 */
    text-overflow: ellipsis;
    /* 隐藏超出元素的文本 */
    overflow: hidden;
}

:deep(.el-tabs) {
    background: transparent !important;
}

:deep(.el-table-cell) {
    height: 45px;
}

:deep(.el-table-header-cell) {
    height: 60px;
}

:deep(.index .cell) {
    padding: 0;
}

:deep(.line .high-pre) {
    background: transparent;
}

:deep(.column) {
    padding-top: 10px;
    color: #ff5a5a;
}

:deep(.sql-content) {
    width: calc(100% - 30px) !important;
    padding: 0 15px !important;
}
</style>

<style>
.result-col {

    .el-pagination > .is-first,
    .el-pagination__goto,
    .el-pagination__classifier {
        margin-left: 10px !important;
        font-size: 16px !important;
        font-weight: var(--font-weight);
    }

    .number,
    .more,
    .el-pagination__editor,
    .el-pagination__sizes {
        font-size: 16px !important;
        height: calc(var(--font-size) * 2);
    }

    .el-select {
        height: 100%;
    }

    .btn-prev,
    .btn-next {
        width: 80px !important;
        font-size: 16px !important;
        font-family: inherit !important;
        height: calc(var(--font-size) * 2);
    }
}

.connection-dialog .el-drawer__body {
    padding: 0 5px !important;
}

.request-table .el-table__body tr > td.el-table__cell.cell-select,
.glass .el-table.request-table .el-table__body tr.el-table__row.current-row > td.el-table__cell.cell-select {
    background-color: rgb(0 112 255 / 20%) !important;
}

.glass .el-table.request-table .el-table__body tr.current-row > td.el-table-fixed-column--left {
    background-color: #f1f1f1 !important;
}
</style>